package io.zhiller.camerademo.utils

import android.Manifest
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Matrix
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.LifecycleCameraController
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

object CameraUtils {

  data class ImageInfo(
    val name: String,
    val savePath: String,
    val saveTime: Long
  )

  @OptIn(ExperimentalPermissionsApi::class)
  @Composable
  fun RequestCameraPermission(content: @Composable () -> Unit) {
    val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
    if (cameraPermissionState.status.isGranted) {
      content()
    } else if (cameraPermissionState.status.shouldShowRationale) {
      Toast.makeText(
        LocalContext.current,
        "Camera permission is needed to run this app",
        Toast.LENGTH_SHORT
      ).show()
    } else {
      SideEffect {
        cameraPermissionState.run { launchPermissionRequest() }
      }
      Toast.makeText(
        LocalContext.current,
        "Camera permission is needed to run this app",
        Toast.LENGTH_SHORT
      ).show()
    }
  }

  /**
   * 捕获并保存照片的挂起函数
   *
   * @param context 上下文，用于访问应用资源和保存照片
   * @param controller 生命周期相机控制器，用于控制相机操作
   * @param imageInfo 图像信息，可能包含有关图像的额外数据
   * @param onSuccess 成功回调，当照片捕获并保存成功时调用
   * @param onError 失败回调，当照片捕获失败时调用
   */
  suspend fun takeAndSavePhoto(
    context: Context,
    controller: LifecycleCameraController,
    imageInfo: ImageInfo?,
    onSuccess: () -> Unit = { Log.i("CameraUtils", "Take Photo Success") },
    onError: () -> Unit = { Log.i("CameraUtils", "Take Photo Failed") }
  ) {
    // 调用控制器的takePicture方法来捕获照片
    controller.takePicture(
      ContextCompat.getMainExecutor(context),
      object : ImageCapture.OnImageCapturedCallback() {
        /**
         * 当照片捕获成功时调用
         *
         * @param image 捕获的图像代理对象，包含图像数据和信息
         */
        override fun onCaptureSuccess(image: ImageProxy) {
          super.onCaptureSuccess(image)

          // 创建并应用旋转矩阵，以修正图像的方向
          val matrix = Matrix().apply {
            postRotate(
              image.imageInfo.rotationDegrees.toFloat()
            )
          }

          // 根据捕获的图像和旋转矩阵创建位图
          val imageBitmap = Bitmap.createBitmap(
            image.toBitmap(),
            0, 0,
            image.width, image.height,
            matrix, true
          )

          // 在IO调度器上启动协程，以异步方式保存照片
          CoroutineScope(Dispatchers.IO).launch {
            savePhoto(context, imageBitmap, imageInfo, onSuccess)
          }
        }

        /**
         * 当照片捕获失败时调用
         *
         * @param exception 捕获失败的异常对象，包含错误信息
         */
        override fun onError(exception: ImageCaptureException) {
          super.onError(exception)
          onError()
        }
      }


    )
  }

  @RequiresApi(Build.VERSION_CODES.Q)
  suspend fun savePhoto(
    context: Context,
    bitmap: Bitmap,
    imageInfo: ImageInfo?,
    onSuccess: () -> Unit = { Log.i("CameraUtils", "Take Photo Success") },
    onError: () -> Unit = { Log.i("CameraUtils", "Take Photo Failed") }
  ) {

    val info = imageInfo
      ?: ImageInfo(
        name = "${System.currentTimeMillis()}_image" + ".jpeg",
        savePath = "Pictures/CameraX-Image",
        saveTime = System.currentTimeMillis()
      )

    // 在IO线程上下文中执行耗时操作，避免阻塞主线程
    withContext(Dispatchers.IO) {
      // 获取内容解析器，用于访问媒体库
      val resolver: ContentResolver = context.contentResolver

      // 定义图片集合的URI，用于插入新的图片
      val imageCollection = MediaStore.Images.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY
      )

      // 创建ContentValues对象以存储图像的相关信息
      val imageContentValues: ContentValues = ContentValues().apply {
        // 设置图像的显示名称，使用时间戳来确保唯一性
        put(
          MediaStore.Images.Media.DISPLAY_NAME,
          info.name
        )

        // 设置图像的相对路径，保存在DCIM目录下以应用名称为子文件夹
        put(
          MediaStore.MediaColumns.RELATIVE_PATH,
          info.savePath
        )

        // 设置图像的MIME类型为JPEG
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpg")

        // 设置图像的拍摄时间为当前时间戳
        put(MediaStore.MediaColumns.DATE_TAKEN, info.saveTime)

        // 标记图像为待处理状态，通常用于表示正在保存或处理中的媒体文件
        put(MediaStore.MediaColumns.IS_PENDING, 1)
      }

      // 插入新的图片记录到媒体库，并获取其URI
      val imageMediaStoreUri: Uri? = resolver.insert(
        imageCollection, imageContentValues
      )

      // 如果成功获取到URI，则尝试将Bitmap数据写入该URI对应的文件
      imageMediaStoreUri?.let { uri ->
        try {
          // 获取输出流，用于写入Bitmap数据
          resolver.openOutputStream(uri)?.let { outputStream ->
            // 将Bitmap压缩为JPEG格式，并写入输出流
            bitmap.compress(
              Bitmap.CompressFormat.JPEG, 100, outputStream
            )
          }

          // 清空ContentValues，并更新IS_PENDING状态，表示图片处理完成
          imageContentValues.clear()
          imageContentValues.put(
            MediaStore.MediaColumns.IS_PENDING, 0
          )
          // 更新媒体库中的图片记录
          resolver.update(
            uri, imageContentValues, null, null
          )
          onSuccess()
        } catch (e: Exception) {
          // 如果发生异常，打印错误信息并删除未完成的图片记录
          e.printStackTrace()
          onError()
          resolver.delete(uri, null, null)
        }
      }
    }
  }

  fun captureAndSaveImage(
    imageInfo: ImageInfo?,
    imageCapture: ImageCapture,
    context: Context,
    onSuccess: () -> Unit = {},
    onError: () -> Unit = {}
  ) {
    val info = imageInfo
      ?: ImageInfo(
        name = "CameraxImage.jpeg",
        savePath = "Pictures/CameraX-Image",
        saveTime = System.currentTimeMillis()
      )

    // 创建ContentValues以设置图像文件的元数据
    val contentValues = ContentValues().apply {
      put(MediaStore.MediaColumns.DISPLAY_NAME, info.name)
      put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
      put(MediaStore.Images.Media.RELATIVE_PATH, info.savePath)
      put(MediaStore.MediaColumns.DATE_TAKEN, info.saveTime)
    }

    // 构建ImageCapture.OutputFileOptions对象，用于指定图像保存的位置和元数据
    val outputOptions = ImageCapture.OutputFileOptions
      .Builder(
        context.contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        contentValues
      )
      .build()

    // 捕获图像并保存到指定位置
    imageCapture.takePicture(
      outputOptions,
      ContextCompat.getMainExecutor(context),
      object : ImageCapture.OnImageSavedCallback {
        // 当图像成功保存时调用
        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) = onSuccess()

        // 当图像保存失败时调用
        override fun onError(exception: ImageCaptureException) = onError()
      })
  }

  /**
   * 获取当前上下文的相机提供者实例
   * 该函数使用协程来异步获取相机提供者，以避免阻塞主线程
   *
   * @return ProcessCameraProvider 相机提供者实例
   */
  suspend fun getCameraProvider(context: Context): ProcessCameraProvider =
    suspendCoroutine { continuation ->
      // 获取相机提供者的实例
      ProcessCameraProvider.getInstance(context).also { cameraProvider ->
        // 当相机提供者准备好后，执行监听器中的代码
        cameraProvider.addListener({
          // 使用协程继续执行，传递相机提供者实例
          continuation.resume(cameraProvider.get())
        }, ContextCompat.getMainExecutor(context))
      }
    }
}